<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title></title>
</head>
<body>
<div id="nig"></div>
<button id="button">敢开老娘草拟吗</button>
<script>
    //《一. base》
    // 一个函数和对其周围状态（lexical environment，词法环境）的引用捆绑在一起（或者说函数被引用包围），这样的组合就是闭包（closure）。
    // 也就是说，闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中，每当创建一个函数，闭包就会在函数创建的同时被创建出来。

    // function init() {
    //     let name = '仙女薇'// name 是一个被 outer 创建的局部变量
    //     function displayName() {// displayName() 是内部函数，一个闭包
    //         console.log(name)// 使用了父函数中声明的变量
    //     }
    //     displayName()
    // }
    // init()

    // init() 创建了一个局部变量 name 和一个名为 displayName() 的函数。displayName() 是定义在 init() 里的内部函数，
    // 并且仅在 init() 函数体内可用。请注意，displayName() 没有自己的局部变量。然而，因为它可以访问到外部函数的变量，
    // 所以 displayName() 可以使用父函数 init() 中声明的变量 name 。

    // 使用这个 JSFiddle 链接运行该代码后发现， displayName() 函数内的 alert() 语句成功显示出了变量 name 的值（该变量在其父函数中声明）。
    // 这个词法作用域的例子描述了分析器如何在函数嵌套的情况下解析变量名。词法（lexical）一词指的是，
    // 词法作用域根据源代码中声明变量的位置来确定该变量在何处可用。嵌套函数可访问声明于它们外部作用域的变量。

    // function makeFunc() {
    //     let name = "李梦薇";
    //     let age = 22
    //
    //     function displayName() {
    //         console.log(name + age)
    //     }
    //
    //     return displayName;
    // }
    //
    // let myFunc = makeFunc();
    // myFunc()

    // 在一些编程语言中，一个函数中的局部变量仅存在于此函数的执行期间。一旦 makeFunc() 执行完毕，你可能会认为 name 变量将不能再被访问。
    // 然而，因为代码仍按预期运行，所以在 JavaScript 中情况显然与此不同。

    // 原因在于，JavaScript中的函数会形成了闭包。 闭包是由函数以及声明该函数的词法环境组合而成的。
    // 该环境包含了这个闭包创建时作用域内的任何局部变量。在本例子中，myFunc 是执行 makeFunc 时创建的 displayName 函数实例的引用。
    // displayName 的实例维持了一个对它的词法环境（变量 name、age存在于其中）的引用。因此，当 myFunc 被调用时，
    // 变量 name 仍然可用，其值 李梦薇 就被传递到console.log中。

    // function makeAdder(x) {
    //     return function(y) {
    //         return x + y;
    //     };
    // }
    //
    // var add5 = makeAdder(5);
    // var add10 = makeAdder(10);
    //
    // console.log(add5(2));  // 7
    // console.log(add10(2)); // 12

    // 在这个示例中，我们定义了 makeAdder(x) 函数，它接受一个参数 x ，并返回一个新的函数。返回的函数接受一个参数 y，并返回x+y的值。

    // 从本质上讲，makeAdder 是一个函数工厂 — 他创建了将指定的值和它的参数相加求和的函数。在上面的示例中，我们使用函数工厂创建了两个新函数 —
    // 一个将其参数和 5 求和，另一个和 10 求和。
    // add5 和 add10 都是闭包。它们共享相同的函数定义，但是保存了不同的词法环境。在 add5 的环境中，x 为 5。而在 add10 中，x 则为 10。

    // 《二. 用闭包模拟私有方法》
    // let Counter = (function () {
    //     let privateCounter = 2247
    //     function changeBy(val) {
    //         privateCounter += val
    //     }
    //     return {
    //         increment() {
    //             changeBy(1)
    //         },
    //         decrement() {
    //             changeBy(-1)
    //         },
    //         getValue() {
    //             return privateCounter
    //         }
    //     }
    // })()
    //
    // console.log(Counter.getValue())
    // Counter.increment()
    // Counter.increment()
    // console.log(Counter.getValue())
    // Counter.decrement()
    // console.log(Counter.getValue())

    // 在之前的示例中，每个闭包都有它自己的词法环境；而这次我们只创建了一个词法环境，为三个函数所共享：
    // Counter.increment，Counter.decrement 和 Counter.value。
    // 该共享环境创建于一个立即执行的匿名函数体内。这个环境中包含两个私有项：名为 privateCounter 的变量和名为 changeBy 的函数。
    // 这两项都无法在这个匿名函数外部直接访问。必须通过匿名函数返回的三个公共函数访问。
    // 这三个公共函数是共享同一个环境的闭包。多亏 JavaScript 的词法作用域，它们都可以访问 privateCounter 变量和 changeBy 函数。

    // let makeCounter = function() {
    //     let privateCounter = 4396;
    //     function changeBy(val) {
    //         privateCounter += val;
    //     }
    //     return {
    //         increment: function() {
    //             changeBy(1);
    //         },
    //         decrement: function() {
    //             changeBy(-1);
    //         },
    //         value: function() {
    //             return privateCounter;
    //         }
    //     }
    // };
    //
    // let Counter1 = makeCounter();
    // Counter1.increment()
    // Counter1.increment()
    // Counter1.increment()
    // let Counter2 = makeCounter();
    // console.log(Counter2.value())

    // 请注意两个计数器 Counter1 和 Counter2 是如何维护它们各自的独立性的。每个闭包都是引用自己词法作用域内的变量 privateCounter 。
    // 每次调用其中一个计数器时，通过改变这个变量的值，会改变这个闭包的词法环境。然而在一个闭包内对变量的修改，不会影响到另外一个闭包中的变量。
    // 以这种方式使用闭包，提供了许多与面向对象编程相关的好处 —— 特别是数据隐藏和封装。

    // 《三. 在循环中创建闭包》
    // 方法1：使用高阶函数（函数工厂）
    // 方法2：使用立即调用匿名函数表达式，并将词法环境声明在这个匿名函数中
    // 方法3：使用let关键字声明闭包的词法环境

    // 《四. 深究(MDN上没有)》
    // 关于常驻内存：2012年后除IE外几乎所以浏览器采用标记-清除法进行GC，GC Root为window对象。在makeFunc执行后，
    // 由于变量myFunc引用innerFunc，所以innerFunc所代表的这个堆中的函数对象不会被GC。
    // 闭包真正想说的：不是innerFunc不会被GC，而是count【也不会】被GC，因为它是innerFunc引用的词法环境。
    // 所以在这个例子里面，在第二次点击button，运行myFunc的时候，count不是1。
    // （瞎叫）闭包是对函数first class的又一次诠释，函数与变量拥有等价地位，函数对变量的使用就等同于变量对变量的引用。
    // 所以，函数的词法环境不会被GC。
    function makeFunc() {
        let count = 1;
        function innerFunc() {
            console.log(count);
            count++
        }
        return innerFunc
    }

    let myFunc = makeFunc();

    document.getElementById('button').addEventListener('click',myFunc)
</script>
</body>
</html>
